{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"https://raw.githubusercontent.com/Qiskit/qiskit-tutorials/master/images/qiskit-heading.png\" alt=\"Note: In order for images to show up in this jupyter notebook you need to select File => Trusted Notebook (and the image needs to be where this notebook is expecting it)\" width=\"500 px\" align=\"left\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# _*Quantum Animations*_\n",
    "\n",
    "The latest version of this notebook is available on https://github.com/qiskit/qiskit-tutorial.\n",
    "\n",
    "***\n",
    "### Contributors\n",
    "James R. Wootton, IBM Research\n",
    "***"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the notebook on [random terrain generation](random_terrain_generation.ipynb), we used qubits to make black and white pixel images.\n",
    "\n",
    "Here we will use the same principle to make colour images. For this we use the fact that each colour can be expressed using the [RGB_color_model](https://en.wikipedia.org/wiki/RGB_color_model). This uses three values to expressed a colour, which tell us how much red, green and blue must be mixed together. These values are typically in the range from 0 to 255. For example, black is (0,0,0), white is (255,255,255) and red is (255,0,0). With this in mind, we can simply have three separate monochromatic process for these three colour channels, and then combine them at the end.\n",
    "\n",
    "Another new aspect we'll introduce here is the ability to encode an existing image. For example, consider the 'image' below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "plumber = {(0, 0): (255, 255, 255), (0, 1): (255, 255, 255), (0, 2): (255, 255, 255), (0, 3): (255, 0, 0), (0, 4): (255, 0, 0), (0, 5): (255, 0, 0), (0, 6): (255, 255, 255), (0, 7): (92, 64, 51), (1, 0): (255, 255, 255), (1, 1): (255, 0, 0), (1, 2): (255, 255, 255), (1, 3): (0, 0, 255), (1, 4): (0, 0, 255), (1, 5): (0, 0, 255), (1, 6): (0, 0, 255), (1, 7): (92, 64, 51), (2, 0): (255, 0, 0), (2, 1): (255, 0, 0), (2, 2): (255, 192, 203), (2, 3): (255, 0, 0), (2, 4): (0, 0, 255), (2, 5): (0, 0, 255), (2, 6): (255, 255, 255), (2, 7): (255, 255, 255), (3, 0): (255, 0, 0), (3, 1): (255, 0, 0), (3, 2): (255, 192, 203), (3, 3): (255, 0, 0), (3, 4): (0, 0, 255), (3, 5): (0, 0, 255), (3, 6): (255, 255, 255), (3, 7): (255, 255, 255), (4, 0): (255, 255, 255), (4, 1): (255, 0, 0), (4, 2): (255, 255, 255), (4, 3): (0, 0, 255), (4, 4): (0, 0, 255), (4, 5): (0, 0, 255), (4, 6): (0, 0, 255), (4, 7): (92, 64, 51), (5, 0): (255, 255, 255), (5, 1): (255, 255, 255), (5, 2): (255, 255, 255), (5, 3): (255, 0, 0), (5, 4): (255, 0, 0), (5, 5): (255, 0, 0), (5, 6): (255, 255, 255), (5, 7): (92, 64, 51), (6, 0): (255, 255, 255), (6, 1): (255, 255, 255), (6, 2): (255, 255, 255), (6, 3): (255, 255, 255), (6, 4): (255, 255, 255), (6, 5): (210, 180, 140), (6, 6): (255, 255, 255), (6, 7): (255, 255, 255), (7, 0): (255, 255, 255), (7, 1): (255, 255, 255), (7, 2): (255, 255, 255), (7, 3): (107, 92, 72), (7, 4): (210, 180, 140), (7, 5): (107, 92, 72), (7, 6): (255, 255, 255), (7, 7): (255, 255, 255)}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is an 8x8 pixel picture of a tanuki plumber, expressed as a Python dictionary. For each coordinate of a pixel, it gives the corresponding RGB values. This can then be turned into a proper image. In this notebook, we'll use the `PIL` package to do this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADUklEQVR4nO3dsY1VMRBA0We0HbE5ENID2wGtUAAxCQ2QISHyzSjo0cEmXyPLuucUYI8sXTmz133f19HW2j3BY04//8O92z0A7CQA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQ9je9w+vv906bPx/8Db3IDkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKTN/w8w/T7939fZ9ad9eN49QZobgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBtDb/ef61rdof7WqPrTzv+fIb/f/j68nl0fTcAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQNrx/wPwttP/B/j369vo+m4A0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgLSn3QNwtun3+7///D26vhuANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIG3d9z27wRpd/hoef5zz2csNQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZC2vnx8v3uGh/z487p7hIe8fHrePcJDTj9/NwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRA2n8dmy0sa+CUzwAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<PIL.PngImagePlugin.PngImageFile image mode=RGB size=256x256 at 0x108C537F0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from PIL import Image\n",
    "from IPython.display import display\n",
    "\n",
    "def save_image(image,filename='image.png',scale=None):\n",
    "\n",
    "    img = Image.new('RGB',(8,8))\n",
    "\n",
    "    for x in range(img.size[0]):\n",
    "        for y in range(img.size[1]):\n",
    "            img.load()[x,y] = image[x,y]\n",
    "\n",
    "    if scale:\n",
    "        img = img.resize((256,256))\n",
    "\n",
    "    img.save('outputs/'+filename)\n",
    "\n",
    "    \n",
    "save_image(plumber,scale=[300,300],filename='image1.png')\n",
    "display(Image.open('outputs/image1.png'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will now turn this image into a quantum state. More precisely, we'll turn him into three states, with one for each colour channel. We will assign a bit string to each coordinate in the image, and use the probabilities of those bit strings as the value for the colour channels of that coordinate.\n",
    "\n",
    "The above image has $8x8=2^6$ pixels, and so needs 6 qubits."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "n = 6"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we set the probabilities for each bit string, we need to account for the fact that we do not have the freedom to do this arbitrarily. As probabilities, they must all be in the range from 0 to 1, and they must all sum to 1. We can do this simply by renormalizing. The corresponding amplitudes of the quantum state are then the square roots of these values.\n",
    "\n",
    "First we must decide which bitstrings belong to which values. This can be done in exactly the same way as for the terrain generation notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "L = int(2**(n/2))\n",
    "\n",
    "grid = {}\n",
    "for y in range(L):\n",
    "    for x in range(L):\n",
    "        grid[(x,y)] = ''\n",
    "\n",
    "for (x,y) in grid:\n",
    "    for j in range(n):\n",
    "        if (j%2)==0:\n",
    "            xx = np.floor(x/2**(j/2))\n",
    "            grid[(x,y)] = str( int( ( xx + np.floor(xx/2) )%2 ) ) + grid[(x,y)]\n",
    "        else:\n",
    "            yy = np.floor(y/2**((j-1)/2))\n",
    "            grid[(x,y)] = str( int( ( yy + np.floor(yy/2) )%2 ) ) + grid[(x,y)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following function then takes an image and a mapping of coordinates to bit strings and makes three quantum states, one for each colour channel. Each is expressed as a Python lists of amplitudes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "def image2state(image,grid):\n",
    "    \n",
    "    N = len(grid)\n",
    "    state = [[0]*N,[0]*N,[0]*N] # different states for R, G and B\n",
    "\n",
    "    for pos in image:\n",
    "        for j in range(3):\n",
    "            state[j][ int(grid[pos],2) ] = np.sqrt( image[pos][j] ) # amplitude is square root of colour value\n",
    "\n",
    "    for j in range(3):        \n",
    "        Z = sum(np.absolute(state[j])**2)\n",
    "        state[j] = [amp / np.sqrt(Z) for amp in state[j]] # amplitudes are normalized\n",
    "        \n",
    "    return state\n",
    "\n",
    "\n",
    "state = image2state(plumber,grid)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can load the states into quantum circuits using the `initialize()` function of Qiskit, which initializes a circuit with a given quantum state. We can then manipulate the image in a quantum way. Here we'll use the statevector simulator to do this, which outputs the final quantum state."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit import *\n",
    "\n",
    "backend = Aer.get_backend('statevector_simulator')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The final statevector is a set of amplitudes, but our encoding of RGB was done via the probabilities. For this reason, it would be useful to have the standard counts dictionary as we get from other simulators and real quantum devices. The following function performs the conversion."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def ket2counts (ket):\n",
    "    \n",
    "    counts = {}\n",
    "    N = len(ket)\n",
    "    n = int( np.log(N)/np.log(2) ) # figure out the qubit number that this state describes\n",
    "    for j in range(N):\n",
    "        string = bin(j)[2:]\n",
    "        string = '0'*(n-len(string)) + string\n",
    "        counts[string] = np.absolute(ket[j])**2 # square amplitudes to get probabilities\n",
    "    \n",
    "    return counts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's have a simple example where we load the image in and then extract the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "q = QuantumRegister(n)\n",
    "\n",
    "counts = []\n",
    "for j in range(3): # j=0 for red, j=1 for green, j=2 for blue\n",
    "    qc = QuantumCircuit(q)\n",
    "    qc.initialize( state[j], q )\n",
    "    job = execute(qc, backend)\n",
    "    counts.append( ket2counts( job.result().get_statevector() ) )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The only remaining job is to turn the three dictionaries of counts back into an image. This is going to be a bit ambiguous, because we need to undo our earlier normalization step. To do this, we'll assume that, for every colour channel, there is at least one pixel with the value 255. This means that the maximum probaility for each colour channel will be given the value 255, and all other values are assigned proportionally. This is done by the following function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADU0lEQVR4nO3dsY1VMRBA0We0HbE5EFID0AGtUAA5AQ2QISHyzSjo0cEmXyPLuucUYI8sXTmz133f19HW2j3BY04//8O92T0A7CQA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQ9je9w+vv906bPx/8Dr3IDkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKTN/w8w/T7935fZ9ae9e949QZobgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBtDb/ef61rdof7WqPrTzv+fIb/f/j65ePo+m4A0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgLTj/wfgdaf/D/Dv17fR9d0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGlPuwfgbNPv93//+Xt0fTcAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQNq673t2gzW6/DU8/jjns5cbgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBtfXr/dvcMD/nx52X3CA/5/OF59wgPOf383QCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaf8BHBstLNplIikAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<PIL.PngImagePlugin.PngImageFile image mode=RGB size=256x256 at 0x115D449B0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def counts2image(counts,grid):\n",
    "    \n",
    "    image = { pos:[0,0,0] for pos in grid}\n",
    "\n",
    "    for j in range(3):\n",
    "\n",
    "        rescale = 255/max(counts[j].values()) # rescale so that largest probability becomes value of 255\n",
    "\n",
    "        for pos in image:\n",
    "            try:\n",
    "                image[pos][j] = int( rescale*counts[j][grid[pos]] )\n",
    "            except:\n",
    "                image[pos][j] = int( rescale*counts[j][grid[pos]] )\n",
    "\n",
    "    for pos in image:\n",
    "        image[pos] = tuple(image[pos])\n",
    "\n",
    "    return image\n",
    "\n",
    "\n",
    "save_image( counts2image(counts,grid), scale=[300,300], filename='image2.png' )\n",
    "display(Image.open('outputs/image2.png'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our brave adventurer has been inside a quantum circuit and emerged intact!\n",
    "\n",
    "Now let's do some simple manipulation. We'll simply apply a rotation around the y axis over the course of a number of frames."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "frame_num = 20"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These will then be compiled into an animated PNG using the `apng` package. Note that you'll need to run this notebook yourself in order to see this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "from apng import APNG\n",
    "import os\n",
    "\n",
    "state = image2state(plumber,grid)\n",
    "\n",
    "filenames = []\n",
    "for f in range(frame_num):\n",
    "        \n",
    "    circuits = []\n",
    "    for j in range(3):\n",
    "        qc = QuantumCircuit(q)\n",
    "        qc.initialize(state[j],q)\n",
    "        qc.ry(2*np.pi*f/frame_num,q)\n",
    "        circuits.append( qc )\n",
    "\n",
    "    job = execute(circuits, backend)\n",
    "\n",
    "    counts = []\n",
    "    for j in range(3):\n",
    "        counts.append( ket2counts( job.result().get_statevector(circuits[j]) ) )\n",
    "        \n",
    "    frame = counts2image(counts,grid)\n",
    "    \n",
    "    filename = 'frame_'+str(f)+'.png'\n",
    "    save_image( counts2image(counts,grid), scale=[300,300], filename=filename)\n",
    "    filenames.append( 'outputs/' + filename )\n",
    "\n",
    "APNG.from_files(filenames,delay=250).save('outputs/animation.png')\n",
    "\n",
    "for file in filenames:\n",
    "    os.remove(file)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](outputs/animation.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the above example, each qubit from each circuit is rotated in the y axis at the same rate. These rotations all cover the range from $0$ to $2\\pi$ over the course of the frames. To explore different possibilities, we could choose different fractions of $2\\pi$ to be covered over the frames. Setting this $>1$ will cause a faster rotation, and $<1$ will cause a slower one. Setting it to an integer will mean that the corresponding qubit will return to its original state by the final frame (or almost, anyway). For a non-integer, it will not.\n",
    "\n",
    "Below you can choose values of these fractions for each qubit for each colour channel via Jupyter widgets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "fed9936d403349159e6014cc57cee232",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Tab(children=(VBox(children=(FloatSlider(value=1.0, description='qubit 0', max=5.0, step=0.01), FloatSlider(va…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import ipywidgets as widgets\n",
    "\n",
    "def make_box():\n",
    "    children = [widgets.FloatSlider(value=1,max=5.0,step=0.01,description='qubit '+str(qubit),show=True) for qubit in range(n)]\n",
    "    box = widgets.VBox(children)\n",
    "    return box\n",
    "\n",
    "tab = widgets.Tab()\n",
    "tab.children = [make_box() for j in range(3)]\n",
    "channels = ['Red Channel','Green Channel','Blue Channel']\n",
    "for j in range(3):\n",
    "    tab.set_title(j, channels[j])\n",
    "    \n",
    "tab"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once you've chosen, run the cells below to extract the values and create the animation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "fraction = [[],[],[]]\n",
    "for j in range(3):\n",
    "    for qubit in range(n):\n",
    "        fraction[j].append( tab.children[j].children[qubit].value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "state = image2state(plumber,grid)\n",
    "\n",
    "filenames = []\n",
    "for f in range(frame_num):\n",
    "        \n",
    "    circuits = []\n",
    "    for j in range(3):\n",
    "        qc = QuantumCircuit(q)\n",
    "        qc.initialize(state[j],q)\n",
    "        for qubit in range(n):\n",
    "            qc.ry(2*np.pi*fraction[j][qubit]*f/frame_num,q[qubit])\n",
    "        circuits.append( qc )\n",
    "\n",
    "    job = execute(circuits, backend)\n",
    "\n",
    "    counts = []\n",
    "    for j in range(3):\n",
    "        counts.append( ket2counts( job.result().get_statevector(circuits[j]) ) )\n",
    "        \n",
    "    frame = counts2image(counts,grid)\n",
    "    \n",
    "    filename = 'frame_'+str(f)+'.png'\n",
    "    save_image( counts2image(counts,grid), scale=[300,300], filename=filename)\n",
    "    filenames.append( 'outputs/' + filename )\n",
    "\n",
    "APNG.from_files(filenames,delay=250).save('outputs/new_animation.png')\n",
    "\n",
    "for file in filenames:\n",
    "    os.remove(file)    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](outputs/new_animation.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "keywords = {'Topics': ['Creative','Images'], 'Commands': ['`ry`','`initialize`']}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
